#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 11-Nov-09 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives

using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Runtime.InteropServices;
#endregion

namespace Thn.Interface.Vcl
{
    #region Event Argument Classes

    #region Popup Closed Event Args
    /// <summary>
    /// Contains event information for a <see cref="PopupClosed"/> event.
    /// </summary>
    public class PopupClosedEventArgs : EventArgs
    {
        /// <summary>
        /// The popup form.
        /// </summary>
        private Form popup = null;

        /// <summary>
        /// Gets the popup form which is being closed.
        /// </summary>
        public Form Popup
        {
            get
            {
                return this.popup;
            }
        }

        /// <summary>
        /// Constructs a new instance of this class for the specified
        /// popup form.
        /// </summary>
        /// <param name="popup">Popup Form which is being closed.</param>
        public PopupClosedEventArgs(Form popup)
        {
            this.popup = popup;
        }
    }
    #endregion

    #region Popup Cancel Event Args
    /// <summary>
    /// Arguments to a <see cref="PopupCancelEvent"/>.  Provides a
    /// reference to the popup form that is to be closed and 
    /// allows the operation to be cancelled.
    /// </summary>
    public class PopupCancelEventArgs : EventArgs
    {
        /// <summary>
        /// Whether to cancel the operation
        /// </summary>
        private bool cancel = false;
        /// <summary>
        /// Mouse down location
        /// </summary>
        private Point location;
        /// <summary>
        /// Popup form.
        /// </summary>
        private Form popup = null;

        /// <summary>
        /// Constructs a new instance of this class.
        /// </summary>
        /// <param name="popup">The popup form</param>
        /// <param name="location">The mouse location, if any, where the
        /// mouse event that would cancel the popup occured.</param>
        public PopupCancelEventArgs(Form popup, Point location)
        {
            this.popup = popup;
            this.location = location;
            this.cancel = false;
        }

        /// <summary>
        /// Gets the popup form
        /// </summary>
        public Form Popup
        {
            get
            {
                return this.popup;
            }
        }

        /// <summary>
        /// Gets the location that the mouse down which would cancel this 
        /// popup occurred
        /// </summary>
        public Point CursorLocation
        {
            get
            {
                return this.location;
            }
        }

        /// <summary>
        /// Gets/sets whether to cancel closing the form. Set to
        /// <c>true</c> to prevent the popup from being closed.
        /// </summary>
        public bool Cancel
        {
            get
            {
                return this.cancel;
            }
            set
            {
                this.cancel = value;
            }
        }

    }
    #endregion

    #endregion

    #region Delegates
    /// <summary>
    /// Represents the method which responds to a <see cref="PopupClosed"/> event.
    /// </summary>
    public delegate void PopupClosedEventHandler(object sender, PopupClosedEventArgs e);

    /// <summary>
    /// Represents the method which responds to a <see cref="PopupCancel"/> event.
    /// </summary>
    public delegate void PopupCancelEventHandler(object sender, PopupCancelEventArgs e);
    #endregion

    #region Popup Window Helper
    /// <summary>
    /// A class to assist in creating popup windows like Combo Box drop-downs and Menus.
    /// This class includes functionality to keep the title bar of the popup owner form
    /// active whilst the popup is displayed, and to automatically cancel the popup
    /// whenever the user clicks outside the popup window or shifts focus to another 
    /// application.
    /// </summary>
    internal class PopupWindowHelper : NativeWindow
    {
        #region Unmanaged Code
        [DllImport("user32", CharSet = CharSet.Auto)]
        private extern static int SendMessage(IntPtr handle, int msg, int wParam, IntPtr lParam);

        [DllImport("user32", CharSet = CharSet.Auto)]
        private extern static int PostMessage(IntPtr handle, int msg, int wParam, IntPtr lParam);

        private const int WMmACTIVATE = 0x006;
        private const int WMmACTIVATEAPP = 0x01C;
        private const int WMmNCACTIVATE = 0x086;

        [DllImport("user32")]
        private extern static void keybdmevent(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);

        private const int KEYEVENTFmKEYUP = 0x0002;
        #endregion

        #region Member Variables
        /// <summary>
        /// Event Handler to detect when the popup window is closed
        /// </summary>
        private EventHandler popClosedHandler = null;
        /// <summary>
        /// Message filter to detect mouse clicks anywhere in the application
        /// whilst the popup window is being displayed.
        /// </summary>
        private PopupWindowHelperMessageFilter filter = null;
        /// <summary>
        /// The popup form that is being shown.
        /// </summary>
        private Form PopupForm = null;
        /// <summary>
        /// The owner of the popup form that is being shown:
        /// </summary>
        private Form OwnerForm = null;
        /// <summary>
        /// Whether the popup is showing or not.
        /// </summary>
        private bool popupShowing = false;
        /// <summary>
        /// Whether the popup has been cancelled, notified by PopupCancel,
        /// rather than closed.
        /// </summary>
        private bool skipClose = false;

        /// <summary>
        /// Gets the owner control of this helper
        /// </summary>
        public Control OwnerControl = null;
        /// <summary>
        /// Signify that filtering message for closing up popup should be disregarded.
        /// </summary>
        public bool SuppressCancelMessageFiltering = false;
        bool mOwnerFormTopMostBackup = false;
        #endregion

        #region Properties

        #region Location
        private Point mLocation = Point.Empty;
        /// <summary>
        /// Gets/Sets offset to location (top left) of owner control
        /// </summary>
        public Point Location
        {
            get { return mLocation; }
            set { mLocation = value; }
        }
        #endregion
        
        #region Is Popping
        /// <summary>
        /// Checks whether popup form is being shown
        /// </summary>
        public bool IsPopping
        {
            get { return popupShowing; }
        }
        #endregion

        #endregion

        #region Events
        /// <summary>
        /// Raised when the popup form is closed.
        /// </summary>
        public event PopupClosedEventHandler PopupClosed;
        /// <summary>
        /// Raised when the Popup Window is about to be cancelled.  The
        /// <see cref="PopupCancelEventArgs.Cancel"/> property can be
        /// set to <c>true</c> to prevent the form from being cancelled.
        /// </summary>
        public event PopupCancelEventHandler PopupCancel;
        #endregion

        #region Public methods

        #region Show Popup
        /// <summary>
        /// Shows the specified Form as a popup window, keeping the
        /// Owner's title bar active and preparing to cancel the popup
        /// should the user click anywhere outside the popup window.
        /// <para>Put as much initialisation code as possible
        /// into the popup form's constructor, rather than the <see cref="System.Windows.Forms.Load"/>
        /// event as this will improve visual appearance.</para>
        /// </summary>
        /// <param name="OwnerControl">Control that calls this popup</param>
        /// <param name="PopupForm">Window to show as a popup</param>
        public void ShowPopup(Control OwnerControl, Form PopupForm)
        {
            Form NewOwnerForm = OwnerControl.FindForm();

            if (this.Handle != NewOwnerForm.Handle) this.AssignHandle(NewOwnerForm.Handle);

            this.OwnerForm = NewOwnerForm;
            this.PopupForm = PopupForm;

            // Start checking for the popup being cancelled
            System.Windows.Forms.Application.AddMessageFilter(filter);

            // Set the location of the popup form:
            int width = PopupForm.Width;
            int height = PopupForm.Height;
            Point position = OwnerControl.PointToScreen(new Point(OwnerControl.Left, OwnerControl.Top));
            position.X -= OwnerControl.Left;
            position.Y -= OwnerControl.Top;
            if (mLocation != Point.Empty)
            {
                position.Offset(mLocation);
            }
            Rectangle rScreen = Screen.GetBounds(position);
            if (position.X + width > rScreen.Width)
            {//right is clipped
                position.X += OwnerControl.Width - width;
            }
            if (position.Y + height + OwnerControl.Height > rScreen.Height)
            {//bottom is clipped
                position.Y -= height;
            }
            else position.Y += OwnerControl.Height;

            PopupForm.StartPosition = FormStartPosition.Manual;
            PopupForm.Location = position;

            // Make it owned by the window that's displaying it:
            OwnerForm.AddOwnedForm(PopupForm);
            // Respond to the Closed event in case the popup
            // is closed by its own internal means
            popClosedHandler = new EventHandler(popupClosed);
            PopupForm.Closed += popClosedHandler;

            //TlnEditHere
            mOwnerFormTopMostBackup = OwnerForm.TopMost;
            OwnerForm.TopMost = false;
            PopupForm.TopMost = true;


            // Show the popup:
            this.popupShowing = true;

            //=========================================
            //
            // TLN modified on 21-Mar-2009
            //
            // Fix the bug still show icon of the popup form when show
            //
            PopupForm.ShowInTaskbar = false;
            //=========================================

            PopupForm.Show();
            PopupForm.Activate();

            #region Uncomment this if you like debugging challenges
            // A little bit of fun.  We've shown the popup,
            // but because we've kept the main window's
            // title bar in focus the tab sequence isn't quite
            // right.  This can be fixed by sending a tab,
            // but that on its own would shift focus to the
            // second control in the form.  So send a tab,
            // followed by a reverse-tab.

            // Send a Tab command:
            //keybdmevent((byte)Keys.Tab, 0, 0, 0);
            //keybdmevent((byte)Keys.Tab, 0, KEYEVENTFmKEYUP, 0);

            // Send a reverse Tab command:
            //keybdmevent((byte)Keys.ShiftKey, 0, 0, 0);
            //keybdmevent((byte)Keys.Tab, 0, 0, 0);
            //keybdmevent((byte)Keys.Tab, 0, KEYEVENTFmKEYUP, 0);
            //keybdmevent((byte)Keys.ShiftKey, 0, KEYEVENTFmKEYUP, 0);
            #endregion

            // Start filtering for mouse clicks outside the popup
            filter.Popup = PopupForm;
        }
        #endregion

        #region Cancel Popup
        /// <summary>
        /// Force to cancel the popup right away.
        /// </summary>
        public void CancelPopup()
        {
            this.filter.RequestCancel();
        }
        #endregion

        #region Close Popup
        /// <summary>
        /// Called when the popup is being hidden.
        /// </summary>
        public void ClosePopup()
        {
            if (this.popupShowing)
            {
                if (!skipClose)
                {
                    // Raise event to owner
                    OnPopupClosed(new PopupClosedEventArgs(this.PopupForm));
                }
                skipClose = false;

                // Make sure the popup is closed and we've cleaned
                // up:
                if (this.OwnerForm != null) OwnerForm.RemoveOwnedForm(this.PopupForm);
                this.popupShowing = false;
                if (this.PopupForm != null) this.PopupForm.Closed -= popClosedHandler;
                this.popClosedHandler = null;
                //this.popup.Close();
                if (this.PopupForm != null) PopupForm.Hide();
                // No longer need to filter for clicks outside the
                // popup.
                System.Windows.Forms.Application.RemoveMessageFilter(filter);

                // If we did something from the popup which shifted
                // focus to a new form, like showing another popup
                // or dialog, then Windows won't know how to bring
                // the original owner back to the foreground, so
                // force it here:
                if (this.OwnerForm != null)
                {
                    this.OwnerForm.TopMost = mOwnerFormTopMostBackup;//TlnEditHere
                    this.OwnerForm.Activate();
                }

                // Null out references for GC
                this.PopupForm = null;
                this.OwnerForm = null;

            }
        }
        #endregion

        #endregion

        #region Protected methods

        #region Wnd Proc
        /// <summary>
        /// Subclasses the owning form's existing Window Procedure to enables the 
        /// title bar to remain active when a popup is show, and to detect if
        /// the user clicks onto another application whilst the popup is visible.
        /// </summary>
        /// <param name="m">Window Procedure Message</param>
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            if (this.popupShowing)
            {
                // check for WMmACTIVATE and WMmNCACTIVATE
                if (m.Msg == WMmNCACTIVATE)
                {
                    // Check if the title bar will made inactive:
                    if (((int)m.WParam) == 0)
                    {
                        // If so reactivate it.
                        SendMessage(this.Handle, WMmNCACTIVATE, 1, IntPtr.Zero);

                        // Note it's no good to try and consume this message;
                        // if you try to do that you'll end up with windows
                        // that don't respond.
                    }

                }
                else if (m.Msg == WMmACTIVATEAPP)
                {
                    // Check if the application is being deactivated.
                    if ((int)m.WParam == 0)
                    {
                        if (!SuppressCancelMessageFiltering)
                        {
                            // It is so cancel the popup:
                            ClosePopup();
                            // And put the title bar into the inactive state:
                            PostMessage(this.Handle, WMmNCACTIVATE, 0, IntPtr.Zero);
                        }
                    }
                }
            }
        }
        #endregion

        #region On Popup Closed
        /// <summary>
        /// Raises the <see cref="PopupClosed"/> event.
        /// </summary>
        /// <param name="e"><see cref="PopupClosedEventArgs"/> describing the
        /// popup form that is being closed.</param>
        protected virtual void OnPopupClosed(PopupClosedEventArgs e)
        {
            if (this.PopupClosed != null)
            {
                this.PopupClosed(this, e);
            }
        }
        #endregion

        #region On Popup Cancel
        /// <summary>
        /// Raises the <see cref="PopupCancel"/> event.
        /// </summary>
        /// <param name="e"><see cref="PopupCancelEventArgs"/> describing the
        /// popup form that about to be cancelled.</param>
        protected virtual void OnPopupCancel(PopupCancelEventArgs e)
        {
            if (this.PopupCancel != null)
            {
                this.PopupCancel(this, e);
                if (!e.Cancel)
                {
                    skipClose = true;
                }
            }
        }
        #endregion

        #endregion

        #region Inner Methods

        #region popup Closed
        /// <summary>
        /// Responds to the <see cref="System.Windows.Forms.Form.Closed"/>
        /// event from the popup form.
        /// </summary>
        /// <param name="sender">Popup form that has been closed.</param>
        /// <param name="e">Not used.</param>
        void popupClosed(object sender, EventArgs e)
        {
            ClosePopup();
        }
        #endregion

        #region popup Cancel
        void popupCancel(object sender, PopupCancelEventArgs e)
        {
            OnPopupCancel(e);
        }
        #endregion

        #endregion

        #region Constructor / Destructor
        /// <summary>
        /// Default destructor.
        /// </summary>
        ~PopupWindowHelper()
        {
        }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <remarks>Use the <see cref="System.Windows.Forms.NativeWindow.AssignHandle"/>
        /// method to attach this class to the form you want to show popups from.</remarks>
        public PopupWindowHelper(Control aOwnerControl)
        {
            OwnerControl = aOwnerControl;
            filter = new PopupWindowHelperMessageFilter(this);
            filter.PopupCancel += new PopupCancelEventHandler(popupCancel);
        }
        #endregion
    }
    #endregion

    #region Popup Window Helper Message Filter
    /// <summary>
    /// A Message Loop filter which detect mouse events whilst the popup form is shown
    /// and notifies the owning <see cref="PopupWindowHelper"/> class when a mouse
    /// click outside the popup occurs.
    /// </summary>
    internal class PopupWindowHelperMessageFilter : IMessageFilter
    {
        #region Fields
        private const int WMmLBUTTONDOWN = 0x201;
        private const int WMmRBUTTONDOWN = 0x204;
        private const int WMmMBUTTONDOWN = 0x207;
        private const int WMmNCLBUTTONDOWN = 0x0A1;
        private const int WMmNCRBUTTONDOWN = 0x0A4;
        private const int WMmNCMBUTTONDOWN = 0x0A7;

        /// <summary>
        /// The owning <see cref="PopupWindowHelper"/> object.
        /// </summary>
        private PopupWindowHelper mOwner = null;
        #endregion

        #region Properies

        #region Popup
        /// <summary>
        /// The popup form
        /// </summary>
        private Form popup = null;
        /// <summary>
        /// Gets/sets the popup form which is being displayed.
        /// </summary>
        public Form Popup
        {
            get
            {
                return this.popup;
            }
            set
            {
                this.popup = value;
            }
        }
        #endregion

        #endregion

        #region Events
        /// <summary>
        /// Raised when the Popup Window is about to be cancelled.  The
        /// <see cref="PopupCancelEventArgs.Cancel"/> property can be
        /// set to <c>true</c> to prevent the form from being cancelled.
        /// </summary>
        public event PopupCancelEventHandler PopupCancel;
        /// <summary>
        /// Raises the <see cref="PopupCancel"/> event.
        /// </summary>
        /// <param name="e">The <see cref="PopupCancelEventArgs"/> associated 
        /// with the cancel event.</param>
        protected virtual void RaiseCancelPopup(PopupCancelEventArgs e)
        {
            if (this.PopupCancel != null)
            {
                this.PopupCancel(this, e);
            }
            if (!e.Cancel) //popup is allowed to be canceled
            {
                mOwner.ClosePopup();
                //owner.CancelPopup();
                // Clear reference for GC
                popup = null;
            }
        }
        #endregion

        #region Pre Filter Message
        /// <summary>
        /// Checks the message loop for mouse messages whilst the popup
        /// window is displayed.  If one is detected the position is
        /// checked to see if it is outside the form, and the owner
        /// is notified if so.
        /// </summary>
        /// <param name="m">Windows Message about to be processed by the
        /// message loop</param>
        /// <returns><c>true</c> to filter the message, <c>false</c> otherwise.
        /// This implementation always returns <c>false</c>.</returns>
        public bool PreFilterMessage(ref Message m)
        {
            if (this.popup != null)
            {
                switch (m.Msg)
                {
                    case WMmLBUTTONDOWN:
                    case WMmRBUTTONDOWN:
                    case WMmMBUTTONDOWN:
                    case WMmNCLBUTTONDOWN:
                    case WMmNCRBUTTONDOWN:
                    case WMmNCMBUTTONDOWN:
                        CheckMouseCursor();
                        break;
                }
            }
            return false;
        }
        #endregion

        #region Request Cancel
        /// <summary>
        /// Force cancel
        /// </summary>
        public void RequestCancel()
        {
            Point cursorPos = Cursor.Position;
            RaiseCancelPopup(new PopupCancelEventArgs(popup, cursorPos));
        }
        #endregion

        #region Check Cursor In Control
        bool CheckCursorInControl(Point CursorPosition, Control control)
        {
            Point p = control.PointToClient(CursorPosition);
            return (p.X >= 0) && (p.X <= control.Width) && (p.Y >= 0) && (p.Y <= control.Height);
        }
        #endregion

        #region Check Mouse Cursor
        /// <summary>
        /// Checks the mouse location and calls the OnCancelPopup method
        /// if the mouse is outside the popup form.		
        /// </summary>
        private void CheckMouseCursor()
        {
            // Get the cursor location
            Point cursorPos = Cursor.Position;
            Control OwnerControl = mOwner.OwnerControl;
            Form PopupForm = popup;

            bool Inside = CheckCursorInControl(cursorPos, OwnerControl) || CheckCursorInControl(cursorPos, PopupForm);

            if (!Inside) RequestCancel();
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Constructs a new instance of this class and sets the owning
        /// object.
        /// </summary>
        /// <param name="owner">The <see cref="PopupWindowHelper"/> object
        /// which owns this class.</param>
        public PopupWindowHelperMessageFilter(PopupWindowHelper owner)
        {
            this.mOwner = owner;
        }
        #endregion
    }
    #endregion
}
